/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 *
 */

import { noop } from '@baize/wdk';
import { FileSet, MetricLogType, TaskInputValue } from '@ohos/hvigor';
import * as fse from 'fs-extra';
import path from 'path';

import { PackingToolOptions } from '../../builder/inner-java-command-builder/packing-tool-options.js';
import { ModulePathInfoIml } from '../../common/iml/module-path-info-iml.js';
import { BuildArtifactConst, BuildArtifactExtension, BuildDirConst } from '../../const/build-directory-const.js';
import { CommonConst } from '../../const/common-const.js';
import { DeviceTypeConst } from '../../const/device-type-const.js';
import { CodeType } from '../../enum/code-type-enum';
import { CodeModel } from '../../model/code-source/code-model';
import { LegacyTargetSourceSetModel } from '../../model/source-set/source-set-model.js';
import { PackInfo } from '../../options/configure/pack-info-options.js';
import { getIntersection } from '../../utils/array-util.js';
import { OhosLogger } from '../../utils/log/ohos-logger.js';
import { ProcessUtils } from '../../utils/process-utils.js';
import { TaskNames } from '../common/task-names.js';
import { ModuleTargetData } from '../data/hap-task-target-data.js';
import { TargetTaskService } from '../service/target-task-service.js';

import { LegacyGenerateMetadata } from './legacy-generate-metadata.js';
import { LegacyMakePackInfo } from './legacy-make-pack-info.js';
import { LegacySignLiteBin } from './legacy-sign-lite-bin.js';
import { LegacySyscapTransform } from './legacy-syscap-transform.js';
import LegacyFATask = TaskNames.LegacyFATask;
import PackHapPathsObj = PackInfo.PackHapPathsObj;
import { SignTypeEnum } from '../../enum/sign-type-enum';
import { CacheNativeLibs } from '../cache-native-libs.js';
import { ModuleTaskService } from '../service/module-task-service.js';
import { SignUtil } from '../sign/sign-util';
import { OhosHapTask } from '../task/ohos-hap-task';

/**
 * 老模型的打包Hap包任务
 *
 * @since 2022/1/10
 */
export class LegacyPackageHap extends OhosHapTask {
  private _log: OhosLogger = OhosLogger.getLogger(LegacyPackageHap.name);

  private readonly needPackBin: boolean;
  private readonly unSignBinPath: string;
  private readonly signBinPath: string;
  private readonly binOutPath: string;
  private readonly libPath: string;
  private readonly rpcidSc: string;
  private readonly apkPath: string;
  private readonly allPackHapPathsObj: PackHapPathsObj[] = [];
  private readonly arkCodeModel: CodeModel | undefined;
  private readonly jsCodeModel: CodeModel | undefined;
  protected readonly signUtil: SignUtil;
  protected readonly signType: SignTypeEnum;

  declareExecutionTool(): string {
    return this.sdkInfo.getPackageTool();
  }

  declareExecutionCommand(): string {
    if (this.needPackBin) {
      return this.getHapBinCommand().toString();
    } else {
      const allCommand: string[] = [];
      for (let i = 0; i < this.allPackHapPathsObj.length; i++) {
        allCommand.push(this.getHapCommand(this.allPackHapPathsObj[i]).toString());
      }
      return allCommand.join();
    }
  }

  declareInputs(): Map<string, TaskInputValue> {
    return new Map<string, TaskInputValue>().set('needPackBin', this.needPackBin);
  }

  declareInputFiles(): FileSet {
    if (this.needPackBin) {
      const fileSet = new FileSet();
      if (this.targetData.hasLiteDeviceInTarget()) {
        fileSet.addEntry(this.signUtil._signingConfig ? this.signBinPath : this.unSignBinPath, { isDirectory: false });
      }
      return fileSet;
    }
    const fileSet = new FileSet();
    fileSet.addEntry(this.libPath, { isDirectory: false });
    this.allPackHapPathsObj.forEach((packHapPathsObj: PackHapPathsObj) => {
      if (this.arkCodeModel || this.jsCodeModel) {
        fileSet.addEntry(packHapPathsObj.assetsPath, { isDirectory: false });
      }
      fileSet.addEntry(packHapPathsObj.jsonPath, { isDirectory: false });
      fileSet.addEntry(packHapPathsObj.resourcePath, { isDirectory: false });
      fileSet.addEntry(packHapPathsObj.indexPath, { isDirectory: false });
      fileSet.addEntry(packHapPathsObj.packInfoPath, { isDirectory: false });
    });

    if (fse.existsSync(this.rpcidSc)) {
      fileSet.addEntry(this.rpcidSc, { isDirectory: false });
    }
    return fileSet;
  }

  declareOutputFiles(): FileSet {
    if (this.needPackBin) {
      return new FileSet().addEntry(this.binOutPath, { isDirectory: false });
    }
    const fileSet = new FileSet();
    this.allPackHapPathsObj.forEach((packHapPathsObj: PackHapPathsObj) => {
      fileSet.addEntry(packHapPathsObj.outPath, { isDirectory: false });
    });
    return fileSet;
  }

  constructor(taskService: TargetTaskService) {
    super(taskService, LegacyFATask.PACKAGE_HAP);
    this.signType = SignTypeEnum.BIN;
    this.signUtil = new SignUtil(this.service, this.signType, this.pathInfo, this.targetData.getProduct(), this.sdkInfo);
    const targetData = this.targetService.getTargetData();
    const pathInfo = targetData.getPathInfo();
    this.needPackBin = this.targetService.needPackBin(targetData);
    this.unSignBinPath = path.resolve(pathInfo.getModuleBinOutput(), targetData.getModuleTargetOutputFileName('', false, BuildArtifactExtension.DOT_BIN));
    this.signBinPath = path.resolve(pathInfo.getModuleBinOutput(), targetData.getModuleTargetOutputFileName('', true, BuildArtifactExtension.DOT_BIN));
    this.binOutPath = path.resolve(
      pathInfo.getModuleBuildOutputPath(),
      targetData.getModuleTargetOutputFileName('', false, undefined, this.targetData.isSingleDeviceTypeTarget() ? undefined : DeviceTypeConst.LITE),
    );
    this.libPath = path.resolve(pathInfo.getIntermediatesProcessLibs());
    this.rpcidSc = path.resolve(pathInfo.getIntermediatesSysCap(), CommonConst.RPCID_SC);
    this.apkPath = path.resolve(pathInfo.getIntermediatesApkDir(), targetData.getApkName());
    this.arkCodeModel = this.moduleModel.getSourceSetByTargetName(this.targetName).getCodeMap().get(CodeType.ETS);
    this.jsCodeModel = this.moduleModel.getSourceSetByTargetName(this.targetName).getCodeMap().get(CodeType.JS);
  }

  protected initTaskDependsForOtherModule() {
    const relatedEntryModules = this.service.getRelatedEntryModules();
    const moduleType = this.service.getModuleModel().getModuleType();
    this.targetData.getTargetDeviceTypeClasses().forEach((deviceTypeClass: string) => {
      if (ModuleTaskService.checkEntryModules(moduleType, relatedEntryModules)) {
        for (const entryName of relatedEntryModules!) {
          this.validateRelatedModule(entryName, this.targetData);
          if (this.module.findModuleByName(entryName)) {
            const entryTarget = this.targetData.findRelatedTargetData(entryName);
            this.pushEntryInfo(this.pathInfo, entryName, this.targetData, entryTarget, deviceTypeClass);
          }
        }
      } else {
        const entryName = '';
        const entryTarget = this.targetData.findRelatedTargetData(entryName);
        this.pushEntryInfo(this.pathInfo, entryName, this.targetData, entryTarget, deviceTypeClass);
      }
    });
  }

  private pushEntryInfo(
    pathInfo: ModulePathInfoIml,
    entryName: string,
    targetData: ModuleTargetData,
    entryTarget?: ModuleTargetData,
    deviceTypeClass?: string,
  ) {
    let outPath: string, jsonPath: string, assetsPath: string, resourcePath: string, shellApkPath: string[] | undefined, dexPath: string | undefined;

    if (deviceTypeClass) {
      if (DeviceTypeConst.LITE === deviceTypeClass && this.needPackBin) {
        return;
      }

      jsonPath = path.resolve(
        pathInfo.getIntermediatesRes(),
        entryName,
        !this.targetData.isSingleDeviceTypeTarget() && !this.needPackBin && deviceTypeClass === DeviceTypeConst.LITE ? DeviceTypeConst.LITE : '',
        CommonConst.CONFIG_JSON,
      );

      assetsPath =
        deviceTypeClass === DeviceTypeConst.LITE
          ? pathInfo.getInterMediatesLoaderOutLitePath()
          : pathInfo.getIntermediatesFaAssetsPath(this.targetData.getProduct());

      if (this.targetData.isSingleDeviceTypeTarget()) {
        outPath = path.resolve(pathInfo.getModuleBuildOutputPath(), targetData.getModuleTargetOutputFileName(entryName, false));
      } else {
        outPath = path.resolve(pathInfo.getModuleBuildOutputPath(), targetData.getModuleTargetOutputFileName(entryName, false, undefined, deviceTypeClass));
      }
    } else {
      jsonPath = path.resolve(pathInfo.getIntermediatesRes(), entryName, CommonConst.CONFIG_JSON);

      outPath = path.resolve(pathInfo.getModuleBuildOutputPath(), targetData.getModuleTargetOutputFileName(entryName, false));

      assetsPath = pathInfo.getIntermediatesFaAssetsPath(this.targetData.getProduct());
    }
    if (DeviceTypeConst.LITE === deviceTypeClass) {
      resourcePath = path.resolve(pathInfo.getIntermediatesLiteSource(), 'assets', this.module.getName(), entryName, BuildDirConst.RESTOOL_BUILD_RESOURCES);
    } else {
      resourcePath = path.resolve(pathInfo.getIntermediatesRes(), entryName, BuildDirConst.RESTOOL_BUILD_RESOURCES);
    }
    const packHapPathsObj: PackHapPathsObj = {
      jsonPath: jsonPath,
      resourcePath: resourcePath,
      indexPath: path.resolve(pathInfo.getIntermediatesRes(), entryName, BuildArtifactConst.RESOURCE_INDEX),
      packInfoPath: path.resolve(pathInfo.getModuleBuildOutputPath(), entryName, BuildArtifactConst.PACK_INFO),
      outPath: outPath,
      shellApkPath: shellApkPath,
      assetsPath: assetsPath,
      dexPath: dexPath,
    };
    this.allPackHapPathsObj.push(packHapPathsObj);
  }

  initTaskDepends() {
    this.declareDepends(LegacyMakePackInfo.name);
    this.declareDepends(CacheNativeLibs.name);
    this.declareDepends(TaskNames.LegacyFATask.MERGE_NODE_ASSETS.name);
    this.declareDepends(LegacyGenerateMetadata.name);
    this.declareDepends(LegacySyscapTransform.name);

    if (this.service.hasLiteDevice()) {
      this.declareDepends(LegacySignLiteBin.name);
    }
  }

  protected async doTaskAction(): Promise<void> {
    for (const deviceTypeClass of this.targetData.getTargetDeviceTypeClasses()) {
      if (DeviceTypeConst.LITE === deviceTypeClass && this.needPackBin) {
        await this.buildHapBinBuilder();
      } else {
        await this.buildHapBuilder();
      }
    }
  }

  private async buildHapBuilder(): Promise<void> {
    for (let i = 0; i < this.allPackHapPathsObj.length; i++) {
      const packHapPathsObj = this.allPackHapPathsObj[i];
      await this.buildHapPackageOptions(packHapPathsObj);
    }
  }

  private async buildHapPackageOptions(packHapPathsObj: PackHapPathsObj): Promise<void> {
    await this.executeCommandList(this.getHapCommand(packHapPathsObj));
  }

  private async buildHapBinBuilder(): Promise<void> {
    await this.executeCommandList(this.getHapBinCommand());
  }

  /**
   * 获得打包hap的命令
   *
   * @param packHapPathsObj 构建HAP包需要的路径
   * @returns {string[]} 生成的命令
   */
  private getHapCommand(packHapPathsObj: PackHapPathsObj): string[] {
    const subTaskName = 'generate HAP packaging command';
    const subDurationEvent = this.durationEvent.createSubEvent(subTaskName, '');
    subDurationEvent.start();

    const packageOptions = new PackingToolOptions();
    packageOptions.addCalledJarFile(this.sdkInfo.getPackageTool());
    if (this.jsCodeModel || this.arkCodeModel) {
      packageOptions
        .addMode('hap')
        .force(true)
        .addLibPath(this.libPath)
        .addJsonPath(packHapPathsObj.jsonPath)
        .addResourcesPath(packHapPathsObj.resourcePath)
        .addAssetsPath(packHapPathsObj.assetsPath)
        .addIndexPath(packHapPathsObj.indexPath)
        .addPackInfoPath(packHapPathsObj.packInfoPath)
        .addOutPath(packHapPathsObj.outPath);
    } else {
      packageOptions
        .addMode('hap')
        .force(true)
        .addLibPath(this.libPath)
        .addJsonPath(packHapPathsObj.jsonPath)
        .addResourcesPath(packHapPathsObj.resourcePath)
        .addIndexPath(packHapPathsObj.indexPath)
        .addPackInfoPath(packHapPathsObj.packInfoPath)
        .addOutPath(packHapPathsObj.outPath);
    }

    if (fse.existsSync(this.rpcidSc)) {
      packageOptions.addSysCapPath(this.rpcidSc);
    }

    subDurationEvent.stop();
    subDurationEvent.setLog(subTaskName, MetricLogType.INFO);
    return packageOptions.build();
  }

  /**
   * 获得打包hap bin的命令
   *
   * @returns {string[]} 生成的命令
   */
  private getHapBinCommand(): string[] {
    const subTaskName = 'generate HAP BIN packaging command';
    const subDurationEvent = this.durationEvent.createSubEvent(subTaskName, '');
    subDurationEvent.start();

    const packageOptions = new PackingToolOptions();
    packageOptions.addCalledJarFile(this.sdkInfo.getPackageTool());
    if (!this.signUtil._signingConfig) {
      packageOptions.addMode('hap').addBinPath(this.unSignBinPath).addOutPath(this.binOutPath).force(true);
    } else {
      packageOptions.addMode('hap').addBinPath(this.signBinPath).addOutPath(this.binOutPath).force(true);
    }

    subDurationEvent.stop();
    subDurationEvent.setLog(subTaskName, MetricLogType.INFO);
    return packageOptions.build();
  }

  /**
   * 执行命令
   *
   * @param command 需要执行的命令
   */
  private async executeCommandList(command: string[]): Promise<void> {
    this._log._printDebugCommand('PackageHap', command);
    const inputData = {
      moduleName: this.moduleName,
      taskName: this.name,
      commandLine: command,
    };

    const submitTaskName = 'submit HAP packaging task to work pool';
    const subToolDurationEvent = this.durationEvent.createSubEvent(submitTaskName, '');
    subToolDurationEvent.start();

    await new ProcessUtils(this.moduleName, this.name).submitExecutionWithoutReturn(this, this.getWorkerPool(), inputData, noop, [], subToolDurationEvent);

    subToolDurationEvent.stop();
    subToolDurationEvent.setLog(submitTaskName, MetricLogType.INFO);
  }

  /**
   * feature关联多entry场景对deviceType进行校验处理
   *
   * @param entryName feature关联的entry模块的moduleName
   * @param targetData moduleTargetData
   * @private
   */
  private validateRelatedModule(entryName: string, targetData: ModuleTargetData) {
    const targetName = targetData.getTargetName();
    const moduleName = targetData.getModuleModel().getName();
    const featureDeviceType = targetData.getTargetDeviceType();
    const relatedEntryTargetData = targetData.findRelatedTargetData(entryName);
    const relatedEntryName = relatedEntryTargetData?.getModuleModel().getName();
    const relatedEntryTargetName = relatedEntryTargetData?.getTargetName();
    const relatedEntryDeviceType = relatedEntryTargetData?.getTargetDeviceType();
    const deviceTypeIntersection = getIntersection(featureDeviceType, relatedEntryDeviceType!);

    // target->config->deviceType配置 则提示build-profile.json5,否则对应模块module.json5
    const targetSourceSetModel = relatedEntryTargetData?.getModuleSourceSetModel() as LegacyTargetSourceSetModel;
    const jsonPath = targetSourceSetModel.getLegacyModuleTargetRes().getJsonPath();
    const filePath =
      relatedEntryTargetData?.getTargetOpt().config?.deviceType === undefined ? jsonPath : relatedEntryTargetData?.getModuleModel().getProfilePath();
    if (deviceTypeIntersection.length === 0) {
      this._log.printErrorExit(
        'NO_DEVICE_TYPE_INTERSECTION_WITH_ENTRY_MODULES',
        [featureDeviceType, targetName, moduleName, relatedEntryDeviceType, relatedEntryTargetName, relatedEntryName, filePath],
        [[targetName]]
      );
    }
  }
}
